package cn.nkpro.elcube.components.financial.services

import cn.nkpro.elcube.co.NkAbstractCustomScriptObject
import cn.nkpro.elcube.co.spel.NkSpELInjection
import cn.nkpro.elcube.docengine.gen.DocIBill
import cn.nkpro.elcube.utils.DateTimeUtilz
import cn.nkpro.elcube.utils.MathUtils
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Component
import org.springframework.util.Assert

import java.util.stream.Collectors

/**
 * 获取指定支付表下某个科目(借方-贷方)汇总金额
 * @accounting.accountTotal (单据ID,凭证类别[可选],业务key[科目别名])
 * 获取指定支付表下某个科目贷方汇总金额
 * @accounting.accountCredit (单据ID,凭证类别[可选],业务key[科目别名])
 * 获取指定支付表下某个科目借方汇总金额
 * @accounting.accountDebit (单据ID,凭证类别[可选],业务key[科目别名])
 *
 * 获取指定账单数据下，截止到时间 的汇总金额
 * @accounting.billTotal (账单数据[List集合],截止时间[含])
 * 获取指定账单数据下，截止到时间 的汇总税额
 * @accounting.billTaxTotal (账单数据[List集合],截止时间[含])
 */
@Component("SpELaccounting")
class NkSpELAccounting extends NkAbstractCustomScriptObject implements NkSpELInjection {

    @Autowired
    private JdbcTemplate jdbcTemplate

    @SuppressWarnings("unused")
    double accountTotal(String docId, Map conditions, Object subjects){

        StringBuilder sql = new StringBuilder(
                "select sum(CREDIT_AMOUNT) CREDIT_TOTAL," +
                        "       sum(DEBIT_AMOUNT)  DEBIT_TOTAL  " +
                        "  from NK_FIN_VOUCHER_I " +
                        " where TARGET_DOC_ID = ? " +
                        "   and STATE = 1")

        List<Object> params = buildParams(sql, new ArrayList<>(), docId, conditions, subjects)

        Map<Double,Double> ret = jdbcTemplate.queryForMap(sql.toString(), params.toArray()) as Map<Double, Double>

        Double creditTotal = ret.getOrDefault('CREDIT_TOTAL',0)
        Double debitTotal = ret.getOrDefault('DEBIT_TOTAL',0)

        MathUtils.currencyValue((debitTotal?:0)-(creditTotal?:0))
    }
    @SuppressWarnings("unused")
    double accountCredit(String docId, Map conditions, Object subjects){

        StringBuilder sql = new StringBuilder(
                "select sum(CREDIT_AMOUNT) CREDIT_TOTAL" +
                        "  from NK_FIN_VOUCHER_I " +
                        " where TARGET_DOC_ID = ? " +
                        "   and STATE = 1")

        List<Object> params = buildParams(sql, new ArrayList<>(), docId, conditions, subjects)

        return MathUtils.currencyValue(jdbcTemplate.queryForObject(sql.toString(), params.toArray(),Double.class)?:0)
    }
    @SuppressWarnings("unused")
    double accountCredit(String docId, Map conditions, Double weight, Object subjects){

        StringBuilder sql = new StringBuilder(
                "select sum(round(CREDIT_AMOUNT * ?,2)) CREDIT_TOTAL" +
                        "  from NK_FIN_VOUCHER_I " +
                        " where TARGET_DOC_ID = ? " +
                        "   and STATE = 1")

        List<Object> params = new ArrayList<>()
        params.add(weight)
        buildParams(sql, params, docId, conditions, subjects)

        return MathUtils.currencyValue(jdbcTemplate.queryForObject(sql.toString(), params.toArray(),Double.class)?:0)
    }
    @SuppressWarnings("unused")
    double accountDebit(String docId, Map conditions, Object subjects){

        StringBuilder sql = new StringBuilder(
                "select sum(DEBIT_AMOUNT)  DEBIT_TOTAL  " +
                        "  from NK_FIN_VOUCHER_I " +
                        " where TARGET_DOC_ID = ? " +
                        "   and STATE = 1")

        List<Object> params = buildParams(sql, new ArrayList<>(), docId, conditions, subjects)

        return MathUtils.currencyValue(jdbcTemplate.queryForObject(sql.toString(), params.toArray(),Double.class)?:0)
    }
    @SuppressWarnings("unused")
    double accountDebit(String docId, Map conditions, Double weight, Object subjects){

        StringBuilder sql = new StringBuilder(
                "select sum(round(DEBIT_AMOUNT * ?,2))  DEBIT_TOTAL  " +
                        "  from NK_FIN_VOUCHER_I " +
                        " where TARGET_DOC_ID = ? " +
                        "   and STATE = 1")

        List<Object> params = new ArrayList<>()
        params.add(weight)
        buildParams(sql, params, docId, conditions, subjects)

        return MathUtils.currencyValue(jdbcTemplate.queryForObject(sql.toString(), params.toArray(),Double.class)?:0)
    }

    private static List<Object> buildParams(StringBuilder sql, List<Object> params, String docId, Map<String,Object> conditions, Object subjects){
        params.add(docId)

        if(conditions){
            conditions.forEach({ k, v ->

                if(k.contains("??")){

                    Collection collection

                    if(v instanceof Collection){
                        collection = v as Collection
                    }else if(v.getClass().isArray()){
                        collection = Arrays.asList((Object[])v)
                    }else{
                        collection = Arrays.asList(v)
                    }
                    params.addAll(collection)

                    String placeholder = collection.stream().map({ e -> '?' }).collect(Collectors.joining(','))
                    sql.append("   and ").append(k.replace("??",placeholder)).append(' ')
                }else{
                    sql.append("   and ").append(k).append(' ')
                    if(v){
                        if(v instanceof Collection){
                            params.addAll(v as Collection)
                        }else if(v.getClass().isArray()){
                            params.addAll(Arrays.asList((Object[])v))
                        }else{
                            params.add(v)
                        }
                    }
                }
            })
        }

        if(subjects instanceof List){
            Assert.notEmpty(subjects,"科目不能为空")
            sql.append("   and BUSINESS_KEY in( ")
            sql.append(subjects.stream().map{i->'?'}.collect(Collectors.joining(',')))
            sql.append(") ")
            params.addAll(subjects)
        }else{
            sql.append("   and BUSINESS_KEY = ? ")
            params.add(subjects)
        }
        return params
    }

    @SuppressWarnings("unused")
    static double billTotal(List<DocIBill> bills, Long endDate){

        double total = MathUtils.currencyValue(
            bills
                .stream()
                .filter { bill->bill.expireDate<=endDate}
                .collect ( Collectors.summingDouble({ e->e.amount})) as double
        )

        DocIBill next = bills.find { bill->bill.expireDate>=endDate }

        // 如果开始日期为null，则不统计
        if(next && next.expireDate>endDate && next.fromDate){
            long totalDays  = DateTimeUtilz.dateDiff(next.expireDate, next.fromDate)
            long activeDays = DateTimeUtilz.dateDiff(endDate, next.fromDate)
            total = MathUtils.currencyValue(total + MathUtils.currencyValue(next.amount / totalDays) * activeDays)
        }

        return total
    }
//    @SuppressWarnings("unused")
//    static double billTaxTotal(List<DocIBill> bills, Long endDate, Double taxRate){
//
//        double total = 0d
//
//        for(DocIBill bill : bills){
//
//            if(bill.fromDate && bill.fromDate<endDate){
//                def amount     = bill.amount
//                // 如果开始日期为null，则不统计。设置日金额为0，最后一天时，会统计为当期全部金额
//                def dayAmount  = MathUtils.currencyValue(amount / DateTimeUtilz.dateDiff(bill.expireDate, bill.fromDate))
//
//                Calendar calendar = Calendar.getInstance()
//                calendar.setTimeInMillis(bill.fromDate * 1000)
//
//                while(calendar.getTimeInMillis()/1000 < bill.expireDate){
//                    calendar.add(Calendar.DAY_OF_MONTH,1)
//
//                    def date = calendar.getTimeInMillis() / 1000 as long
//
//                    // 如果最后一天，取余额
//                    def currAmount = date == bill.expireDate ? amount : dayAmount
//
//                    total = MathUtils.currencyValue(MathUtils.currencyValue(currAmount / (1+taxRate) * taxRate) + total)
//
//                    if(date>=endDate){
//                        break
//                    }
//
//                    amount = MathUtils.currencyValue(amount - dayAmount)
//                }
//            }else if(bill.expireDate<=endDate){
//                total = MathUtils.currencyValue(MathUtils.currencyValue(bill.amount / (1+taxRate) * taxRate) + total)
//            }else{
//                return total
//            }
//        }
//
//        return total
//    }

    @SuppressWarnings("unused")
    static Double ifZero2Null(Number num){
        if(num==null || Math.abs(num.doubleValue())<0.0045){
            return null
        }
        return num
    }
}
